from __future__ import print_function
import sys

# This is not required if you've installed pycparser into
# your site-packages/ with setup.py
#
sys.path.insert(0, '..')

from pycparser import c_parser, c_ast, parse_file
from copy import *
from analyze_funcs import FunctionAnalyzer


class ParamTracker(object):
	def __init__(self,set_functions,set_globals,set_struct_defs):
		self.globals=set_globals
		self.functions=set_functions
		self.struct_defs = set_struct_defs
		self.current_function=None

    
	def visit(self, node):
		method = 'visit_' + node.__class__.__name__
		return getattr(self, method, self.generic_visit)(node)
    
	def generic_visit(self, node):
		if (not (node is None)):
			for c in node.children():
				self.visit(c)
    
	def visit_Constant(self, n):
		return None
        
	def visit_ID(self, n):
		func=self.functions[self.current_function]
		var = None

		if(n.name in func.locals):
			var = func.locals[n.name]
		elif(n.name in self.globals):
			var = self.globals[n.name]
		elif( n.name in func.params):
			var = func.params[n.name]
		if(var == None):
			return None
		return var

	def visit_ArrayRef(self, n):
		#TODO say the tmp_name is ptr+val. need to know which of those to append the PtrRef(indirection) to
		tmp_name=n.name
		var=self.visit(tmp_name)
		assert(var != None)
		assert(len(var.children) == 1)
		var = var.children[0]
		return var


	def visit_StructRef(self, n):
		ret = self.visit(n.name)
		assert(ret != None)
		assert(ret.type.base_type in self.struct_defs)
		field_name = n.field.name
		for child in ret.children:
			if(child.field_name == field_name):
				return child

		print('Error finding field '+field_name+' in struct '+ret[0].type.base_type)
		exit()

	
	#TODO function pointers? probably just assume we have no idea where they are going
	def visit_FuncCall(self, n):
		if(n.name.name in self.functions):
			being_called = self.functions[n.name.name]
			i = 0
			if(n.args != None):
				for arg in n.args.exprs:
					gen=FunctionAnalyzer(self.functions,self.globals,self.struct_defs)
					gen.current_function = self.current_function
					reference=gen.visit(arg)
					reference.var.redirections.append([ reference , being_called.params[being_called.params_list[i] ] ] )
					i = i + 1
		self.visit(n.name)

	def visit_UnaryOp(self, n):

		var = self.visit(n.expr)
		# (ptr+3)++ and ++(ptr+3) is considered invalid lvalue for gcc
		if(n.op == '*'):
			assert(len(var.children) == 1)
			var = var.children[0]
		elif(n.op == '&'):
			assert(var.parent != None)
			var = var.parent
		return var

	def visit_BinaryOp(self, n):
		lvar = self.visit(n.left)
		rvar = self.visit(n.right)
		
		assert(not((rvar != None and rvar.is_ref()) and (lvar != None and lvar.is_ref())))
		if(rvar != None and rvar.is_ref()):
			return rvar
		if(lvar != None and lvar.is_ref()):
			return lvar
		return None
    
	def visit_Assignment(self, n):
		rvar = self.visit(n.rvalue)
		lvar = self.visit(n.lvalue)
		return None
    
	def visit_IdentifierType(self, n):
		return None

	def visit_Decl(self, n, no_type=False):
		if n.init:
			self.visit(n.init)
		return None
    
	def visit_DeclList(self, n):
		self.visit(n.decls[0])
		if len(n.decls) > 1:
			for decl in n.decls[1:]:
				self.visit_Decl(decl, no_type=True) 
    
	def visit_Typedef(self, n):
		return None
    
	def visit_Cast(self, n):
		return None
   
	# is this only called when initializing an array? 
	def visit_ExprList(self, n):
		for expr in n.exprs:
			var = self.visit(expr)
    
	def visit_Enum(self, n):
		if n.values:
			for i, enumerator in enumerate(n.values.enumerators):
				if enumerator.value: 
					self.visit(enumerator.value)
    
	def visit_Struct(self, n):
		if n.decls:
			for decl in n.decls:
				self.visit(decl)
        
	def visit_FuncDef(self, n):
	
		self.current_function=n.decl.name
		self.visit(n.decl)
		# The body is a Compound node
		self.visit(n.body)
		self.current_function=None

	def visit_FileAST(self, n):
		for ext in n.ext:
			self.visit(ext)

	def visit_Compound(self, n):
		if(n.block_items != None):
			for stmt in n.block_items:
				self.visit(stmt)
    
	def visit_ParamList(self, n):
		for param in n.params:
			self.visit(param)

	def visit_Return(self, n):
		var = None
		if n.expr: var = self.visit(n.expr)

	def visit_Break(self, n):
		return None
        
	def visit_Continue(self, n):
		return None
    
	def visit_TernaryOp(self, n):
		var = self.visit(n.cond)
		var = self.visit(n.iftrue)
		var = self.visit(n.iffalse)
    
	def visit_If(self, n):
		if n.cond: 
			var = self.visit(n.cond)
			
		self.visit(n.iftrue)
		if n.iffalse: 
			self.visit(n.iffalse)
    
	def visit_For(self, n):
		if n.init: self.visit(n.init)
		if n.cond: 
			var = self.visit(n.cond)
		if n.next: self.visit(n.next)
		self.visit(n.stmt)

	def visit_While(self, n):
		if n.cond: 
			var = self.visit(n.cond)
		self.visit(n.stmt)

	def visit_DoWhile(self, n):
		self.visit(n.stmt)
		if n.cond: 
			var = self.visit(n.cond)

	def visit_Switch(self, n):
		var = self.visit(n.cond)
		self.visit(n.stmt)
    
	def visit_Case(self, n):
		var = self.visit(n.expr)
		# case expressions are constant but it can't really hurt to try to add them. Maybe a constant declared variable would be useful to know about
		# probably not but it doesn't really hurt to do this anyway. We can set a flag later to tell it not to
		self.visit(n.stmt)
    
	def visit_Default(self, n):
		self.visit(n.stmt)

	def visit_Label(self, n):
		self.visit(n.stmt)

	def visit_Goto(self, n):
		return None

	def visit_EllipsisParam(self, n):
		return None

	def visit_Struct(self, n):
		return None

	def visit_Typename(self, n):
		self._generate_type(n.type)
        
	def visit_Union(self, n):
		return None

	def visit_NamedInitializer(self, n):
		self.visit(n.expr)

	def _generate_type(self, n, modifiers=[]):
		""" Recursive generation from a type node. n is the type node. 
		modifiers collects the PtrDecl, ArrayDecl and FuncDecl modifiers 
		encountered on the way down to a TypeDecl, to allow proper
		generation from it.
		"""
		typ = type(n)
		
        #~ print(n, modifiers)
        
		if typ == c_ast.TypeDecl:
			self.visit(n.type)
            
            # Resolve modifiers.
            # Wrap in parens to distinguish pointer to array and pointer to
            # function syntax.
            #
			for i, modifier in enumerate(modifiers):
				if isinstance(modifier, c_ast.ArrayDecl):
					self.visit(modifier.dim)
				elif isinstance(modifier, c_ast.FuncDecl):
					self.visit(modifier.args)
			return
		elif typ == c_ast.Decl:
			self._generate_type(n.type)
			return
		elif typ == c_ast.Typename:
			self._generate_type(n.type)
			return
		elif typ in (c_ast.ArrayDecl, c_ast.PtrDecl, c_ast.FuncDecl):
			self._generate_type(n.type, modifiers + [n])
			return
		else:
			self.visit(n)
			return
